/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package flowvis;

import flowsimulation.FlowSimulator;
import gui.DensityRendererPanel;
import javax.media.opengl.GL;

/**
 * @author michel
 * Renderer that draws the density field.<br>
 * For that, 3 types of color mapping are available: black-and-white, rainbow, banded.<br>
 * This class extends the class: {@link Renderer}.
 */
public class DensityRenderer extends Renderer {

    /**
     * Is the DensityRenderer visible?
     */
    private static boolean VISIBLE = false;
    /**
     * Black-and-white color mapping
     */
    final static int COLOR_BLACKWHITE = 0;
    /**
     * Rainbow color mapping
     */
    final static int COLOR_RAINBOW = 1;
    /**
     * Banded color mapping
     */
    final static int COLOR_BANDS = 2;
    /**
     * Black body color mapping
     */
    final static int COLOR_BLACKBODY = 3;
    /**
     * Banded black body color mapping
     */
    final static int COLOR_BLACKBODY_BANDS = 4;
    /**
     * Scalar coloring.
     * It should be: <b>COLOR_BLACKWHITE</b>, <b>COLOR_RAINBOW</b>,
     * <b>COLOR_BANDS</b>, <b>COLOR_BLACKBODY</b>, or <b>COLOR_BLACKBODY_BANDS</b>.
     */
    static int scalar_col = 0;
    /**
     * GUI panel for this renderer.
     */
    private DensityRendererPanel panel;

    /**
     * Constructor.
     * @param sim , {@link FlowSimulator} instance.
     */
    public DensityRenderer(FlowSimulator sim) {
        super(sim);
        setVisible(true);
        panel = new DensityRendererPanel(this);
    }
 
    /**
     * @param new value for {@link DensityRenderer#VISIBLE}.
     */
    public static void setVisible( boolean visible ){ VISIBLE = visible; }
    /**
     * @return value of {@link DensityRenderer#VISIBLE}.
     */
    public static boolean getVisible(){ return VISIBLE; }
    /**
     * @return {@link DensityRendererPanel} of this renderer.
     */
    public DensityRendererPanel getPanel() { return panel; }
    /**
     * Returns the current scalar coloring value.
     * @return <b>COLOR_BLACKWHITE</b>, <b>COLOR_RAINBOW</b>
     * <b>COLOR_BANDS</b>, <b>COLOR_BLACKBODY</b>, or <b>COLOR_BLACKBODY_BANDS</b>.
     */
    public int getColoring() { return scalar_col; }
    /**
     * Sets the new scalar coloring value.
     * The value should be: <b>COLOR_BLACKWHITE</b>, <b>COLOR_RAINBOW</b>
     * <b>COLOR_BANDS</b>, <b>COLOR_BLACKBODY</b>, or <b>COLOR_BLACKBODY_BANDS</b>.
     * @param c , new <b>scalar_col</b> value.
     */
    public void setColoring(int c) { scalar_col = c; }

    /**
     * Implements a color palette, mapping the scalar 'value' to a rainbow color RGB.<br>
     * 1. Assign 0.8f to a float named {@code dx}.<br>
     * 2. {@code value} is confirmed to be in the range [0,1].<br>
     * 3. {@code value = (6 - 2 * dx) * value + dx}.
     * 4. For each color, return the max value between 0.0f and a mathematical operation. 
     * @param value
     * @param color 
     */
    static void rainbow(float value, float[] color) {
        final float dx = 0.8f;
        if (value < 0) { value = 0; }
        if (value > 1) { value = 1; }
        value = (6 - 2 * dx) * value + dx;
        color[0] = Math.max(0.0f, (3 - Math.abs(value - 4) - Math.abs(value - 5)) / 2);
        color[1] = Math.max(0.0f, (4 - Math.abs(value - 2) - Math.abs(value - 4)) / 2);
        color[2] = Math.max(0.0f, (3 - Math.abs(value - 1) - Math.abs(value - 2)) / 2);
    }

    /**
     * Implements the blackbody color pallete, mapping the scalar 'value' with transfer functions.<br>
     * 1. Clamp the {@code value} to 0.0 or 1.0 in case it exceeds the range [0.0,1.0].<br>
     * 2. Map the value to the transfer functions {@code RGB = 3.0(value) - b}
     * @param value to color
     * @param color to visualize (RGB).
     */
    static void blackbody( float value, float[] color ){
        // clamp the value
        if (value < 0.0f) { value=0.0f; }
        if (value > 1.0f) { value=1.0f; }
        // Slopes for the transfer functions (y = mx + b).
        final float mr = 3.0f, mg=3.0f, mb=3.0f;
        // R transfer function
        color[0] = Math.min( 1.0f, mr*value );
        // G transfer function
        color[1] = Math.max( 0.0f, Math.min( 1.0f, mg*value-1.0f ) );
        // B transfer function
        color[2] = Math.max( 0.0f, mb*value-2.0f );
    }
    
    /**
     * According to the scalar coloring value, the {@code rgb} values are calculated.<br>
     * 1. Switch to the operation which corresponds to the scalar value.<br>
     * 2.a COLOR_BLACKWHITE: Assign {@code vy} to r, g, and b.<br>
     * 2.b COLOR_RAINBOW: call the procedure {@link DensityRenderer.#rainbow(float, float[])}<br>
     * 2.c COLOR_BANDS: <br>
     * 2.c.1 Assign NLEVELS=7 levels.<br>
     * 2.c.2 {@code vy = vy * NLEVELS}<br>
     * 2.c.3 {@code vy = vy as an integer}<br>
     * 2.c.4 {@code vy = vy / NLEVELS}<br>
     * 2.c.5 call the procedure {@link DensityRenderer.#rainbow(float, float[])}<br>
     * 2.d COLOR_BLACKBODY: call the procedure {@link DensityRenderer.#blackbody(float, float[])}<br>
     * 2.e COLOR_BLACKBODY_BANDS:<br>
     * 2.e.1 Assign NLEVELS=7 levels.<br>
     * 2.e.2 {@code vy = vy * NLEVELS}<br>
     * 2.e.3 {@code vy = vy as an integer}<br>
     * 2.e.4 {@code vy = vy / NLEVELS}<br>
     * 2.c.5 call the procedure {@link DensityRenderer.#blackbody(float, float[])}<br>
     * 3. Assign RGB color by calling {@code GL.glColor3f(R,G,B)}.
     * <br><br>
     * GL methods used:<br>
     * {@code glColor3f(red, green, blue)}: Specify new red, green, and blue
     * values for the current color.<br>
     * - GLfloat  red.<br>
     * - GLfloat  green.<br>
     * - GLfloat  blue.<br>
     * {@see http://www.opengl.org/sdk/docs/man/xhtml/glColor.xml}<br><br>
     * @param gl , related to OpenGL.
     * @param vy , float value to calculate the color to set.
     */
    private static void set_colormap(GL gl, float vy) {
        float[] rgb = new float[3];
        final int NLEVELS = 7;
        switch( scalar_col ){
            case COLOR_BLACKWHITE:
                rgb[0] = rgb[1] = rgb[2] = vy;
                break;
            case COLOR_RAINBOW:
                rainbow(vy, rgb);
                break;
            case COLOR_BANDS:
                vy *= NLEVELS;
                vy = (int) (vy);
                vy /= NLEVELS;
                rainbow(vy, rgb);
                break;
            case COLOR_BLACKBODY:
                blackbody( vy, rgb );
                break;
            case COLOR_BLACKBODY_BANDS:
                vy *= NLEVELS;
                vy = (int) (vy);
                vy /= NLEVELS;
                blackbody( vy, rgb );
                break;
            default: break;
        }
        gl.glColor3f(rgb[0], rgb[1], rgb[2]);
    }

    /**
     * Implementation of {@link Renderer}'s abstract method.<br>
     * 1. If this renderer has 'visible' unchecked in the GUI, finish.<br>
     * 2. Obtain grid size from the flow simulator.<br>
     * 3. Obtain density from the flow simulator, by calling {@code getDensity()}.<br>
     * 4. Call {@code GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL)}<br>
     * 5. Draw smoke algorithm.<br>
     * <br><br>
     * Draw smoke algorithm.<br>
     * 0. i=j=0 <br>
     * 1. For j=0 to grid size - 1.<br>
     * 2.   Get point (x,y) according to weight, height and i, j.<br>
     * 3.   Get the density index according to i, j and grid size.<br>
     * 4.   Set color map according to the density located at the density index.<br>
     * 5.   For i=0 to grid size - 1.<br>
     * 6.     .<br>
     * <br><br>
     * GL methods used:<br>
     * {@code glPolygonMode(face, mode)}: Select a polygon rasterization mode.<br>
     * - face: Specifies the polygons that mode applies to.<br>
     * - mode: Specifies how polygons will be rasterized.<br>
     * - The initial value is GL_FILL for both front- and back-facing polygons.<br>
     * - {@see: http://www.opengl.org/sdk/docs/man/xhtml/glPolygonMode.xml}<br><br>
     * {@code glBegin( mode )} and {@code gl.glEnd()}: Delimit the vertices of
     * a primitive or a group of like primitives.<br>
     * - mode: Specifies the primitive or primitives that will be created
     *   from vertices presented between glBegin and the subsequent glEnd.<br>
     * - {@see: http://www.opengl.org/sdk/docs/man/xhtml/glBegin.xml}<br><br>
     * {@code gl.glVertex2d(px, py)}: Specify x, y coordinates of a vertex.<br>
     * - px: double coordinate of the x value for the vertex.<br>
     * - py: double coordinate of the y value for the vertex.<br>
     * - {@see: http://www.opengl.org/sdk/docs/man/xhtml/glVertex.xml}<br><br>
     * : .<br>
     * - px: double coordinate of the x value for the vertex.<br>
     * - py: double coordinate of the y value for the vertex.<br>
     * <br><br>
     * 
     * @param gl 
     */
    @Override public void visualize(GL gl) {
        if (!getVisible()) return;
        int DIM = simulator.getGridSize();
        // Grid cell width
        double/*fftw_real*/ wn = getWinWidth() / (double) (DIM + 1);
        // Grid cell heigh
        double/*fftw_real*/ hn = getWinHeight() / (double) (DIM + 1);  
        // Get density from the flow simulator.
        double[] rho = simulator.getDensity();
        gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL);
        
        // Draw smoke algorithm
        double px, py;
        int i, j, idx;
        for( j = 0; j < DIM - 1; j++ ){
            gl.glBegin(GL.GL_TRIANGLE_STRIP);
            i = 0;
            px = wn + (float) i * wn;
            py = hn + (float) j * hn;
            idx = (j * DIM) + i;
            set_colormap(gl, (float) rho[idx]);
            gl.glVertex2d(px, py);
            for( i = 0; i < DIM - 1; i++ ){
                px = wn + i * wn;
                py = hn + (j + 1) * hn;
                idx = ((j + 1) * DIM) + i;
                set_colormap(gl, (float) rho[idx]);
                gl.glVertex2d(px, py);
                px = wn + (i + 1) * wn;
                py = hn + j * hn;
                idx = (j * DIM) + (i + 1);
                set_colormap(gl, (float) rho[idx]);
                gl.glVertex2d(px, py);
            }
            px = wn + (float) (DIM - 1) * wn;
            py = hn + (float) (j + 1) * hn;
            idx = ((j + 1) * DIM) + (DIM - 1);
            set_colormap(gl, (float) rho[idx]);
            gl.glVertex2d(px, py);
            gl.glEnd();
        }
    }
}
